package com.tcm.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.tcm.common.enums.CachePrefix;
import com.tcm.config.AppProperties;
import com.tcm.entity.*;
import com.tcm.entity.bo.UserCacheBo;
import com.tcm.entity.dto.BookUpdateDto;
import com.tcm.entity.dto.ImageUploadDto;
import com.tcm.entity.vo.BookChapterStuListResult;
import com.tcm.entity.vo.BookListResult;
import com.tcm.entity.vo.BookVolumeStuListResult;
import com.tcm.exception.DatabaseOperationException;
import com.tcm.exception.EntityInvalidException;
import com.tcm.mapper.BookItemMapper;
import com.tcm.mapper.BookMapper;
import com.tcm.service.*;
import com.tcm.utils.RedisUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.web.multipart.MultipartFile;

import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * @author Guqier
 * @version 1.0
 * @date 2023/7/2 17:17
 */
@Slf4j
@Service
public class BookServiceImpl extends ServiceImpl<BookMapper, Book> implements BookService {

    private static final Integer COURSE_TYPE = 0;// 课程类型

    private static final Integer NODE_TYPE = 1;// 书籍节点类型

    @Autowired
    private BookItemMapper bookItemMapper;

    @Autowired
    private ImageService imageService;

    @Autowired
    private RedisUtil redisUtil;

    @Autowired
    private ItemStudentService itemStudentService;

    @Autowired
    private TeachingService teachingService;

    @Autowired
    private StudentService studentService;

    /**
     * 添加课程
     * @param name 课程名称
     * @param sequence 课程同层级排序，可选
     * @param file 课程封面
     * @param currUserId 当前登录用户ID
     * @return 添加结果，true为成功
     * @throws Exception 异常
     */
    @Override
    @Transactional
    public boolean addCourseInfo(String name, Integer sequence, MultipartFile file, Long currUserId) throws Exception {
        // 上传封面
        ImageUploadDto imageUploadDto = new ImageUploadDto();
        imageUploadDto.setFile(file);
        imageUploadDto.setType(AppProperties.IMAGE_TYPE_COURSE);
        imageUploadDto.setSequence(1);
        imageUploadDto.setName(name);
        Map<String, String> map = imageService.uploadSingle(imageUploadDto, currUserId);

        // 保存课程信息
        Book book = new Book();
        book.setName(name);
        book.setSequence(sequence != null ? sequence : 1);
        book.setParentId(null);
        book.setGrade(0);// 课程为顶级节点
        book.setSequence(book.getSequence() != null ? book.getSequence() : 1);
        book.setType(COURSE_TYPE);
        book.setImageId(Long.parseLong(map.get("imageId")));
        book.setCreateUser(currUserId);
        book.setUpdateUser(currUserId);
        redisUtil.deleteObject(CachePrefix.BOOK_LIST_CACHE.prefix);
        return this.save(book);
    }

    /**
     * 通过ID删除课程（书籍顶底节点）
     * @param id 课程ID
     * @param currUserId 当前登录用户ID
     * @return 删除结果，true为成功。
     */
    @Override
    @Transactional
    public boolean removeCourseInfo(Long id, Long currUserId) throws Exception {
        Book one = this.getById(id);
        if (one == null) {
            return false;// 该课程不存在
        }
        if (one.getType() != 0) {
            return false;// 不是课程
        }
        one.setUpdateUser(currUserId);
        boolean res1 = this.updateById(one);
        boolean res = this.removeById(id);
        if (!res1 || !res){
            throw new DatabaseOperationException("数据库操作失败");
        }
        redisUtil.deleteObject(CachePrefix.BOOK_LIST_CACHE.prefix);
        return true;
    }

    /**
     * 获取课程列表
     * @return 课程列表响应信息
     */
    @Override
    public List<BookListResult> getCourseList(){
        // 查询所有课程
        LambdaQueryWrapper<Book> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(Book::getType, COURSE_TYPE);
        queryWrapper.orderByAsc(Book::getSequence);
        List<Book> list = this.list(queryWrapper);// 查询结果集
        List<BookListResult> res = new ArrayList<>();// 响应结果集
        List<Book> books = this.list();// 书籍所有节点信息
        List<BookItem> bookItems = bookItemMapper.selectList(null);// 所有节点题目关系
        for (Book book : list) {
            BookListResult bookListResult = new BookListResult();
            BeanUtils.copyProperties(book, bookListResult);
            Image image = imageService.getImageById(book.getImageId());
            if (image != null) {
                bookListResult.setImageUrl(image.getRemoteFileUrl());
            }
            // 搜索所有子节点
            List<Book> childNode = findChildNode(book.getId(), books);
            // 查找子节点的所有题目数量
            Integer itemCountByBooks = getItemCountByBooks(childNode, bookItems);
            bookListResult.setItemNum(itemCountByBooks);
            res.add(bookListResult);
        }
        return res;
    }

    /**
     * 修改课程信息
     * @param id 课程ID
     * @param name 课程名称
     * @param sequence 课程同层级排序
     * @param file 封面文件，可为空
     * @param currUserId 当前登录用户ID
     * @return 修改结果，true为成功
     * @throws Exception 异常
     */
    @Override
    @Transactional
    public boolean modifyCourseInfo(Long id, String name, Integer sequence, MultipartFile file, Long currUserId) throws Exception {
        Book book = this.getById(id);
        if (book == null){
            return false;
        }
        book.setName(name);
        book.setSequence(sequence);
        book.setUpdateUser(currUserId);
        // 如果封面不为空
        if (file != null){
            // 上传封面
            ImageUploadDto imageUploadDto = new ImageUploadDto();
            imageUploadDto.setFile(file);
            imageUploadDto.setType(AppProperties.IMAGE_TYPE_COURSE);
            imageUploadDto.setSequence(1);
            imageUploadDto.setName(name);
            Map<String, String> map = imageService.uploadSingle(imageUploadDto, currUserId);
            book.setImageId(Long.parseLong(map.get("imageId")));
        }
        redisUtil.deleteObject(CachePrefix.BOOK_LIST_CACHE.prefix);
        return this.updateById(book);
    }

    /**
     * BFS搜索所有子节点（广度优先）
     * @param bookId 父级ID
     * @param list 所有节点列表
     * @return 所有子节点
     */
    @Override
    public List<Book> findChildNode(Long bookId, List<Book> list){// TODO 待优化
        List<Book> res = new ArrayList<>();
        for (Book book : list) {
            if (book.getParentId() != null && book.getParentId().equals(bookId)){
                res.add(book);
                res.addAll(findChildNode(book.getId(), list));
            }
        }
        return res;
    }

    /**
     * 获取给定节点的所有题目数量
     * @param list 给定节点列表
     * @return 题目数量
     */
    public Integer getItemCountByBooks(List<Book> list, List<BookItem> bookItems){
        int cnt = 0;
        for (Book book : list) {
            // 查找符合要求的题目
            List<BookItem> bookItemList = bookItems.stream()
                    .filter(b -> b.getBookId().equals(book.getId()))
                    .collect(Collectors.toList());
            cnt+=bookItemList.size();
        }
        return cnt;
    }

    /**
     * 添加书籍节点
     * @param bookUpdateDto 书籍更新信息封装类
     * @param currUserId 当前用户ID
     * @return 添加结果，true为成功
     */
    @Override
    @Transactional
    public boolean addBookNode(BookUpdateDto bookUpdateDto, Long currUserId){
        // 校验父级是否存在
        Book parent = this.getById(bookUpdateDto.getParentId());
        if (parent == null){
            return false;
        }
        Book book = new Book();
        BeanUtils.copyProperties(bookUpdateDto, book);
        book.setGrade(parent.getGrade() + 1);// 所属层级比父节点+1
        book.setType(NODE_TYPE);
        book.setSequence(book.getSequence() != null ? book.getSequence() : 1);// 同层级排序默认为1
        book.setCreateUser(currUserId);
        book.setUpdateUser(currUserId);
        redisUtil.deleteObject(CachePrefix.BOOK_LIST_CACHE.prefix);
        redisUtil.deleteObject(CachePrefix.BOOK_CHILD_LIST_CACHE.prefix);
        return this.save(book);
    }

    /**
     * 通过父节点获取下一级节点
     * @param parentId 父节点ID
     * @return 书籍节点列表
     */
    @Override
    public List<BookListResult> getBookNodes(Long parentId){
        LambdaQueryWrapper<Book> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(Book::getParentId, parentId);
        queryWrapper.orderByAsc(Book::getSequence);
        List<Book> list = this.list(queryWrapper);
        List<Book> bookList = this.getBookList();
        List<BookListResult> res = new ArrayList<>();
        List<BookItem> bookItems = bookItemMapper.selectList(null);// 所有节点题目关系
        // 查询所有封面
        LambdaQueryWrapper<Image> imageLambdaQueryWrapper = new LambdaQueryWrapper<>();
        List<Long> imageIds = list.stream().map(Book::getImageId).filter(Objects::nonNull).distinct().collect(Collectors.toList());
        if (CollectionUtils.isEmpty(imageIds)) {
            imageLambdaQueryWrapper.apply("1 != 1");
        }else {
            imageLambdaQueryWrapper.in(Image::getId, imageIds);
        }
        List<Image> images = imageService.list(imageLambdaQueryWrapper);
        for (Book book : list) {
            BookListResult bookListResult = new BookListResult();
            BeanUtils.copyProperties(book, bookListResult);
            // 查询节点下题目数量
            List<Book> childNode = findChildNode(book.getId(), bookList);
            childNode.add(book);// 将当前节点添加到集合里
            Integer itemCount = getItemCountByBooks(childNode, bookItems);
            bookListResult.setItemNum(itemCount);
            // 查询节点图片信息
            images.stream().filter(image -> image.getId().equals(book.getImageId())).findFirst().ifPresent(image -> bookListResult.setImageUrl(image.getRemoteFileUrl()));
            res.add(bookListResult);
        }
        return res;
    }

    /**
     * 修改书籍节点信息
     * @param bookUpdateDto 书籍修改信息封装对象
     * @param currUserId 当前登录用户ID
     * @return 修改结果，true为成功
     */
    @Override
    @Transactional
    public boolean modifyBookNodeInfo(BookUpdateDto bookUpdateDto, Long currUserId){
        // 校验节点是否存在
        Book book = this.getById(bookUpdateDto.getId());
        if (book == null) {
            return false;
        }
        book.setName(bookUpdateDto.getName());
        book.setSequence(bookUpdateDto.getSequence() != null ? bookUpdateDto.getSequence() : 1);
        book.setUpdateUser(currUserId);
        redisUtil.deleteObject(CachePrefix.BOOK_LIST_CACHE.prefix);
        redisUtil.deleteObject(CachePrefix.BOOK_CHILD_LIST_CACHE.prefix);
        return this.updateById(book);
    }

    /**
     * 删除书籍节点
     * @param id 节点ID
     * @param currUserId 当前登录用户ID
     * @return 删除结果，true为成功
     */
    @Override
    @Transactional
    public boolean removeBookNode(Long id, Long currUserId) throws DatabaseOperationException{
        // 校验存在
        Book book = this.getById(id);
        if (book == null) {
            return false;
        }
        book.setUpdateUser(currUserId);
        boolean update = this.updateById(book);
        boolean delete = this.removeById(id);
        if (!update || !delete) {
            throw new DatabaseOperationException("数据库操作失败");
        }
        redisUtil.deleteObject(CachePrefix.BOOK_LIST_CACHE.prefix);
        redisUtil.deleteObject(CachePrefix.BOOK_CHILD_LIST_CACHE.prefix);
        return true;
    }

    /**
     * 通过书籍节点获取课程
     * @param bookId 书籍节点ID
     * @param books 所有书籍列表
     * @return 课程对象
     */
    @Override
    public Book getCourseByBookNodeId(Long bookId, List<Book> books){
        Optional<Book> first = books.stream().filter(book -> book.getId().equals(bookId)).findFirst();
        if (first.isPresent()) {
            Book book = first.get();
            if (book.getParentId() == null){
                return book;
            }else{
                return getCourseByBookNodeId(book.getParentId(), books);
            }
        }
        return null;
    }

    /**
     * 根据父节点获取子书籍节点，不统计题目数量
     * @param parentId 书籍父节点
     * @return 书籍子节点
     */
    @Override
    public List<BookListResult> getBookNodesWithoutItemNum(Long parentId){
        LambdaQueryWrapper<Book> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(Book::getParentId, parentId);
        queryWrapper.orderByAsc(Book::getSequence);
        List<Book> list = this.list(queryWrapper);
        List<BookListResult> res = new ArrayList<>();
        // 查询所有封面
        LambdaQueryWrapper<Image> imageLambdaQueryWrapper = new LambdaQueryWrapper<>();
        List<Long> imageIds = list.stream().map(Book::getImageId).filter(Objects::nonNull).distinct().collect(Collectors.toList());
        if (CollectionUtils.isEmpty(imageIds)) {
            imageLambdaQueryWrapper.apply("1 != 1");
        }else {
            imageLambdaQueryWrapper.in(Image::getId, imageIds);
        }
        List<Image> images = imageService.list(imageLambdaQueryWrapper);
        for (Book book : list) {
            BookListResult bookListResult = new BookListResult();
            BeanUtils.copyProperties(book, bookListResult);
            bookListResult.setItemNum(-1);
            // 查询节点图片信息
            images.stream().filter(image -> image.getId().equals(book.getImageId())).findFirst().ifPresent(image -> bookListResult.setImageUrl(image.getRemoteFileUrl()));
            res.add(bookListResult);
        }
        return res;
    }

    /**
     * 递归查找当前节点父节点ID并返回父节点ID列表
     * @param currBookId 当前书籍节点
     * @param books 所有书籍信息
     * @param parents 父级节点列表
     */
    @Override
    public void getBookAllParentNodesIdByRecursion(Long currBookId, List<Book> books, List<Book> parents){
        for (Book book : books) {
            if (book.getId().equals(currBookId)) {
                parents.add(book);
                if (book.getParentId() != null){
                    getBookAllParentNodesIdByRecursion(book.getParentId(), books, parents);
                }
                break;
            }
        }
    }

    /**
     * 设置书籍节点封面
     * @param bookId 书籍节点ID
     * @param file 封面文件
     * @param currUserId 当前登录用户ID
     * @return 处理结果，true为成功，false为找不到节点
     * @throws Exception 异常
     */
    @Override
    @Transactional
    public Map<String, String> setBookNodeCoverImage(Long bookId, MultipartFile file, Long currUserId) throws Exception {
        // 校验书籍是否存在
        Book book = this.getById(bookId);
        if (book == null) {
            throw new EntityInvalidException("节点不存在");
        }
        // 上传文件
        Map<String, String> map = null;
        if (file != null){
            // 如果不为空，则上传该文件
            ImageUploadDto imageUploadDto = new ImageUploadDto();
            imageUploadDto.setFile(file);
            imageUploadDto.setType(AppProperties.IMAGE_TYPE_COURSE);
            imageUploadDto.setSequence(1);
            imageUploadDto.setName(book.getName());
            map = imageService.uploadSingle(imageUploadDto, currUserId);
            book.setImageId(Long.parseLong(map.get("imageId")));
        }else {
            book.setImageId(null);
        }
        book.setUpdateUser(currUserId);
        if (!this.updateById(book)) {
            throw new DatabaseOperationException("数据库操作失败");
        }
        redisUtil.deleteObject(CachePrefix.BOOK_LIST_CACHE.prefix);
        return map;
    }

    /**
     * 查询题目所属章节
     * @param itemId 题目ID
     * @param bookItems 书籍题目关系信息
     * @param books 书籍信息
     * @return 所属章节信息
     */
    @Override
    public List<List<BookListResult>> getBooksOfItem(Long itemId, List<BookItem> bookItems, List<Book> books){
        // 查询所属章节
        return books
                .stream()
                .filter(book -> bookItems
                        .stream()
                        .filter(bookItem -> bookItem.getItemId().equals(itemId))
                        .map(BookItem::getBookId)
                        .collect(Collectors.toList())
                        .contains(book.getId()))
                .map(book -> {
                    List<Book> parents = new ArrayList<>();
                    this.getBookAllParentNodesIdByRecursion(book.getId(), books, parents);
                    return parents.stream().map(book1 -> {
                        BookListResult result = new BookListResult();
                        BeanUtils.copyProperties(book1, result);
                        result.setItemNum(-1);
                        return result;
                    })
                            .collect(Collectors.toList());
                })
                .collect(Collectors.toList());
    }

    /**
     * 学生获取课程列表
     * @param currUserId 当前用户ID
     * @return 课程列表
     */
    @Override
    public List<BookListResult> getStuCourse(Long currUserId){
        UserCacheBo userCacheBo = redisUtil.getCacheObject(CachePrefix.PC_USER_TOKEN.getCacheKey(currUserId.toString()));
        List<BookListResult> courseList = getCourseList();
        // 判断是普通学生还是社会化学生
        if (userCacheBo.getUserType().equals(AppProperties.STUDENT_USER_TYPE)) {
            Student student= studentService.getStudentByAccount(userCacheBo.getAccount());
            LambdaQueryWrapper<Teaching> queryWrapper = new LambdaQueryWrapper<>();
            queryWrapper.eq(Teaching::getClazzId, student.getClazzId());
            List<Long> courseIds = teachingService.list(queryWrapper).stream().map(Teaching::getBookId).distinct().collect(Collectors.toList());
            return courseList.stream().filter(bookListResult -> courseIds.contains(bookListResult.getId())).collect(Collectors.toList());
        }else {
            // 社会化学员暂时返回全部课程
            return courseList;
        }

    }

    /**
     * 学生获取卷
     * @param courseId 课程ID
     * @return 卷列表
     */
    @Override
    public List<BookVolumeStuListResult> getStuVolume(Long courseId){
        return this.getBookNodesWithoutItemNum(courseId).stream().map(bookListResult -> {
            BookVolumeStuListResult bookVolumeStuListResult = new BookVolumeStuListResult();
            BeanUtils.copyProperties(bookListResult, bookVolumeStuListResult);
            return bookVolumeStuListResult;
        }).collect(Collectors.toList());
    }

    /**
     * 学生获取章节信息
     * @param volumeId 卷ID
     * @param currUserId 当前登录用户ID
     * @return 学生章节列表
     */
    @Override
    public List<BookChapterStuListResult> getStuChapter(Long volumeId, Long currUserId){
        // 查询章节
        List<BookListResult> bookNodes = this.getBookNodes(volumeId);
        // 查询当前用户的学习记录
        UserCacheBo userCacheBo = redisUtil.getCacheObject(CachePrefix.PC_USER_TOKEN.getCacheKey(currUserId.toString()));
        LambdaQueryWrapper<ItemStudent> itemStudentLambdaQueryWrapper = new LambdaQueryWrapper<>();
        itemStudentLambdaQueryWrapper.eq(ItemStudent::getAccount, userCacheBo.getAccount());
        List<ItemStudent> itemStudentList = itemStudentService.list(itemStudentLambdaQueryWrapper);
        // 查询所有节点、题目信息
        List<Long> ids = bookNodes.stream().map(BookListResult::getId).collect(Collectors.toList());
        List<BookItem> bookItems = bookItemMapper.selectList(new LambdaQueryWrapper<BookItem>()
                .in(!CollectionUtils.isEmpty(ids), BookItem::getBookId, ids)
                .apply(CollectionUtils.isEmpty(ids), "1 != 1"));
        // 构造结果
        return bookNodes.stream().map(bookListResult -> {
            // 获取当前节点所有子节点ID
//            List<Long> bookIds = this.findChildNode(bookListResult.getId(), list).stream().map(Book::getId).collect(Collectors.toList());
//            bookIds.add(bookListResult.getId());
            // 查询题目ID
//            LambdaQueryWrapper<BookItem> queryWrapper = new LambdaQueryWrapper<>();
//            queryWrapper.in(BookItem::getBookId, bookIds);
//            if (CollectionUtils.isEmpty(bookIds)) {
//                queryWrapper.apply("1 != 1");
//            }
//            List<Long> itemIds = bookItemMapper.selectList(queryWrapper).stream().map(BookItem::getItemId).collect(Collectors.toList());
            List<Long> itemIds = bookItems.stream().filter(bookItem -> bookItem.getBookId().equals(bookListResult.getId())).map(BookItem::getItemId).collect(Collectors.toList());
            // 查询已学习题目数量
            int count = new Long(itemStudentList.stream().filter(itemStudent -> itemIds.contains(itemStudent.getItemId())).count()).intValue();
            // 构造结果集
            BookChapterStuListResult bookChapterStuListResult = new BookChapterStuListResult();
            BeanUtils.copyProperties(bookListResult, bookChapterStuListResult);
            bookChapterStuListResult.setLearningStatus(count == 0 ? 0 : count == itemIds.size() ? 2 : 1);
            return bookChapterStuListResult;
        }).collect(Collectors.toList());
    }

    /**
     * 获取书籍节点列表（缓存）
     * @return 书籍节点列表
     */
    @Override
    public List<Book> getBookList(){
        List<Book> books = redisUtil.getCacheObject(CachePrefix.BOOK_LIST_CACHE.prefix);
        if (CollectionUtils.isEmpty(books)){
            List<Book> list = this.list();
            redisUtil.setCacheObject(CachePrefix.BOOK_LIST_CACHE.prefix, list, 25, TimeUnit.HOURS);
            return list;
        }
        return books;
    }

    /**
     * 根据书籍父节点ID获取所有子节点（缓存）
     * @param parentId 父节点
     * @return 书籍子节点
     */
    @Override
    public List<Book> getChildBooks(Long parentId){
        List<Book> list = redisUtil.getCacheObject(CachePrefix.BOOK_CHILD_LIST_CACHE.getCacheKey(String.valueOf(parentId)));
        if (CollectionUtils.isEmpty(list)){
            // 查询当前课程下的所有节点
            List<Book> bookList = this.getBookList();
            List<Book> childNode = this.findChildNode(parentId, bookList);
            redisUtil.setCacheObject(CachePrefix.BOOK_CHILD_LIST_CACHE.getCacheKey(String.valueOf(parentId)), childNode, 25, TimeUnit.HOURS);
            return childNode;
        }
        return list;
    }

}
